package com.zz.common.utils.mapper;

import cn.hutool.core.lang.Pair;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.converter.builtin.PassThroughConverter;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import ma.glasnost.orika.metadata.Type;
import ma.glasnost.orika.metadata.TypeFactory;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class BeanMapper {

    private static final MapperFactory defaultMapperFactory;
    private static final MapperFacade defaultMapperFacade;
    private static final Map<Pair<Pair<Type<?>, Type<?>>, Map<String, String>>, MapperFactory> customMapperFactories;

    static {
        defaultMapperFactory = new DefaultMapperFactory.Builder().build();
        defaultMapperFactory.getConverterFactory().registerConverter(new PassThroughConverter(java.util.Map.class));
        defaultMapperFactory.getConverterFactory().registerConverter(new PassThroughConverter(java.util.List.class));
        defaultMapperFacade = defaultMapperFactory.getMapperFacade();
        customMapperFactories = new ConcurrentHashMap<>();
    }

    /**
     * 注册自定义映射规则
     *
     * @param sourceClass
     * @param destinationClass
     * @param attributes       {@code @See}  {@link BeanMapper#register(Type, Type, Map)}
     */
    private static void register(Class<?> sourceClass, Class<?> destinationClass, Map<String, String> attributes) {
        register(getType(sourceClass), getType(destinationClass), attributes);
    }

    private static void register(Type<?> sourceType, Type<?> destinationType, Map<String, String> attributes) {
        Pair<Pair<Type<?>, Type<?>>, Map<String, String>> key = new Pair<>(new Pair<>(sourceType, destinationType), attributes);
        if (!customMapperFactories.containsKey(key)) {
            MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
            mapperFactory.getConverterFactory().registerConverter(new PassThroughConverter(java.util.Map.class));
            mapperFactory.getConverterFactory().registerConverter(new PassThroughConverter(java.util.List.class));
            // 注册自定义映射规则
            ClassMapBuilder<?, ?> builder = mapperFactory.classMap(sourceType, destinationType);
            attributes.forEach(builder::field);
            // 目标类和源类有相同的字段名，则使用默认映射规则，否则使用自定义映射规则
            builder.byDefault().register();
            customMapperFactories.put(key, mapperFactory);
        }
    }

    /**
     * 将源对象source映射到目标对象destinationClass中
     *
     * @param source           源对象
     * @param destinationClass 目标对象类型
     * @param <S>              源对象类型
     * @param <D>              目标对象类型
     * @return 目标对象
     */
    public static <S, D> D map(S source, Class<D> destinationClass) {
        return defaultMapperFacade.map(source, destinationClass);
    }

    /**
     * 将源对象source映射到目标对象destinationType中
     * 预先通过{@link BeanMapper#getType(Class)} 静态获取并缓存Type类型，在此处传入
     *
     * @param source          源对象
     * @param sourceType      源对象类型
     * @param destinationType 目标对象类型
     * @param <S>             源对象类型
     * @param <D>             目标对象类型
     * @return 目标对象
     */
    public static <S, D> D map(S source, Type<S> sourceType, Type<D> destinationType) {
        return defaultMapperFacade.map(source, sourceType, destinationType);
    }

    /**
     * 将源对象source映射到目标对象destinationClass中，并注册自定义映射规则
     *
     * @param source           源对象
     * @param destinationClass 目标对象类型
     * @param attributes       自定义映射规则
     * @param <S>              源对象类型
     * @param <D>              目标对象类型
     * @return 目标对象
     */
    public static <S, D> D map(S source, Class<D> destinationClass, Map<String, String> attributes) {
        register(source.getClass(), destinationClass, attributes);
        Pair<Pair<Type<?>, Type<?>>, Map<String, String>> key = new Pair<>(new Pair<>(getType(source.getClass()), getType(destinationClass)), attributes);
        MapperFactory customMapperFactory = customMapperFactories.get(key);
        return customMapperFactory.getMapperFacade().map(source, destinationClass);
    }

    /**
     * 将源对象source映射到目标对象destinationType中，并注册自定义映射规则
     * 预先通过{@link BeanMapper#getType(Class)} 静态获取并缓存Type类型，在此处传入
     *
     * @param source          源对象
     * @param sourceType      源对象类型
     * @param destinationType 目标对象类型
     * @param attributes      自定义映射规则
     * @param <S>             源对象类型
     * @param <D>             目标对象类型
     * @return 目标对象
     */
    public static <S, D> D map(S source, Type<S> sourceType, Type<D> destinationType, Map<String, String> attributes) {
        register(sourceType, destinationType, attributes);
        Pair<Pair<Type<?>, Type<?>>, Map<String, String>> key = new Pair<>(new Pair<>(sourceType, destinationType), attributes);
        MapperFactory customMapperFactory = customMapperFactories.get(key);
        return customMapperFactory.getMapperFacade().map(source, sourceType, destinationType);
    }

    public static <S, D> List<D> mapList(Iterable<S> sourceList, Class<S> sourceClass, Class<D> destinationClass) {
        return defaultMapperFacade.mapAsList(sourceList, getType(sourceClass), getType(destinationClass));
    }

    public static <S, D> List<D> mapList(Iterable<S> sourceList, Class<S> sourceClass, Class<D> destinationClass, Map<String, String> attributes) {
        register(sourceClass, destinationClass, attributes);
        return ((MapperFactory) customMapperFactories.get(new Pair(new Pair(getType(sourceClass), getType(destinationClass)), attributes))).getMapperFacade().mapAsList(sourceList, TypeFactory.valueOf(sourceClass), TypeFactory.valueOf(destinationClass));
    }

    public static <S, D> List<D> mapList(Iterable<S> sourceList, Type<S> sourceType, Type<D> destinationType) {
        return defaultMapperFacade.mapAsList(sourceList, sourceType, destinationType);
    }

    public static <S, D> List<D> mapList(Iterable<S> sourceList, Type<S> sourceType, Type<D> destinationType, Map<String, String> attributes) {
        register(sourceType, destinationType, attributes);
        return ((MapperFactory) customMapperFactories.get(new Pair(new Pair(sourceType, destinationType), attributes))).getMapperFacade().mapAsList(sourceList, sourceType, destinationType);
    }

    public static <S, D> D[] mapArray(D[] destination, S[] source, Class<D> destinationClass) {
        return defaultMapperFacade.mapAsArray(destination, source, destinationClass);
    }

    public static <S, D> D[] mapArray(D[] destination, S[] source, Class<D> destinationClass, Map<String, String> attributes) {
        register(source[0].getClass(), destinationClass, attributes);
        return ((MapperFactory) customMapperFactories.get(new Pair(new Pair(getType(source[0].getClass()), getType(destinationClass)), attributes))).getMapperFacade().mapAsArray(destination, source, destinationClass);
    }

    public static <S, D> D[] mapArray(D[] destination, S[] source, Type<S> sourceType, Type<D> destinationType) {
        return defaultMapperFacade.mapAsArray(destination, source, sourceType, destinationType);
    }

    public static <S, D> D[] mapArray(D[] destination, S[] source, Type<S> sourceType, Type<D> destinationType, Map<String, String> attributes) {
        register(sourceType, destinationType, attributes);
        return ((MapperFactory) customMapperFactories.get(new Pair(new Pair(sourceType, destinationType), attributes))).getMapperFacade().mapAsArray(destination, source, sourceType, destinationType);
    }

    /**
     * 预先获取orika转换所需要的{@link Type}，避免每次复制都做转换.
     *
     * @param rawType 原始类型
     * @param <E>     泛型类型
     * @return orika转换所需要的{@link Type}
     */
    public static <E> Type<E> getType(Class<E> rawType) {
        return TypeFactory.valueOf(rawType);
    }

}
